/*	EVAL.C: Expresion evaluation functions for
		MicroEMACS

	written 1993 by Daniel Lawrence 			*/

#include	<stdio.h>
#include	"estruct.h"
#include	"eproto.h"
#include	"edef.h"
#include	"elang.h"
#include	"evar.h"

/* initialize the entries in one user variable table */

VOID PASCAL NEAR uv_init(ut)

UTABLE *ut;	/* user variable table to initialize */

{
	register int i;

	for (i=0; i < ut->size; i++) {
		ut->uv[i].u_name[0] = 0;
		ut->uv[i].u_value = (char *)NULL;
	}
}

VOID PASCAL NEAR varinit()	/* initialize the global user variable table */

{
	/* allocate the global user variable table */
	uv_global = uv_head =
		(UTABLE *)room(sizeof(UTABLE) + MAXVARS * sizeof(UVAR));

	/* and set up its fields */
	uv_head->next = (UTABLE *)NULL;
	uv_head->size = MAXVARS;
	uv_head->bufp = (BUFFER *)NULL;
	uv_init(uv_head);
}

VOID PASCAL NEAR uv_clean(ut)	/* discard the contents of a user variable table */

UTABLE *ut;	/* ptr to table to clear */

{
	register int i;

	/* now clear the entries in this one */
	for (i=0; i < ut->size; i++)
		if (ut->uv[i].u_name[0] != 0)
			free(ut->uv[i].u_value);
}

VOID PASCAL NEAR varclean(ut)	/* discard and clear all user variable tables */

UTABLE *ut;	/* table to clear */

{
	/* first clean all the ones under this one */
	if (ut->next != (UTABLE *)NULL)
		varclean(ut->next);

	/* clear the contents of this table */
	uv_clean(ut);

	/* and then deallocate the this table itself */
	free(ut);
}

char *PASCAL NEAR gtfun(fname)	/* evaluate a function */

char *fname;		/* name of function to evaluate */

{
	register int fnum;		/* index to function to eval */
	register int arg;		/* value of some arguments */
	BUFFER *bp;			/* scratch buffer pointer */
	char arg1[NSTRING];		/* value of first argument */
	char arg2[NSTRING];		/* value of second argument */
	char arg3[NSTRING];		/* value of third argument */
	static char result[2 * NSTRING];	/* string result */

	/* look the function up in the function table */
	mklower(fname); /* and let it be upper or lower case */
	fnum = binary(fname, funval, NFUNCS, MINFLEN);

	/* return errorm on a bad reference */
	if (fnum == -1) {
		mlwrite(TEXT244, fname);
/*			"%%No such function as '%s'" */
		return(errorm);
	}

	/* if needed, retrieve the first argument */
	if (funcs[fnum].f_type >= MONAMIC) {
		if (macarg(arg1) != TRUE)
			return(errorm);

		/* if needed, retrieve the second argument */
		if (funcs[fnum].f_type >= DYNAMIC) {
			if (macarg(arg2) != TRUE)
				return(errorm);

			/* if needed, retrieve the third argument */
			if (funcs[fnum].f_type >= TRINAMIC)
				if (macarg(arg3) != TRUE)
					return(errorm);
		}
	}


	/* and now evaluate it! */
	switch (fnum) {
		case UFABBREV:	return(fixnull(ab_lookup(arg1)));
		case UFABS:	return(int_asc(absv(asc_int(arg1))));
		case UFADD:	return(int_asc(asc_int(arg1) + asc_int(arg2)));
		case UFAND:	return(ltos(stol(arg1) && stol(arg2)));
		case UFASCII:	return(int_asc((int)arg1[0]));
		case UFBAND:	return(int_asc(asc_int(arg1) & asc_int(arg2)));
		case UFBIND:	return(transbind(arg1));
		case UFBNOT:	return(int_asc(~asc_int(arg1)));
		case UFBOR:	return(int_asc(asc_int(arg1) | asc_int(arg2)));
		case UFBXOR:	return(int_asc(asc_int(arg1) ^ asc_int(arg2)));
		case UFCALL:	/* construct buffer name to execute */
				result[0] = '[';
				strcpy(&result[1], arg1);
				strcat(result, "]");

				/* find it, return ERROR if it does not exist */
				bp = bfind(result, FALSE, 0);
				if (bp == NULL)
					return(errorm);

				/* execute it and return whats in the $rval */
				dobuf(bp);
				return(fixnull(rval));
		case UFCAT:	strcpy(result, arg1);
				strncat(result, arg2, NSTRING);
				result[NSTRING - 1] = 0;
				return(result);

		case UFCHR:	result[0] = asc_int(arg1);
				result[1] = 0;
				return(result);
		case UFDIV:	if ((arg = asc_int(arg2)) != 0)
					return(int_asc(asc_int(arg1) / arg));
				else {
					mlwrite(TEXT245);
/*						"%%Division by Zero is illegal" */
					return(errorm);
				}
		case UFENV:
#if	ENVFUNC
				return(fixnull(getenv(arg1)));
#else
				return("");
#endif
		case UFEQUAL:	return(ltos(asc_int(arg1) == asc_int(arg2)));
		case UFEXIST:	return(ltos(fexist(arg1)));
		case UFFIND:
				return(fixnull(flook(arg1, TRUE)));
		case UFGREATER: return(ltos(asc_int(arg1) > asc_int(arg2)));
		case UFGROUP:
				arg = asc_int(arg1);
#if	MAGIC
				if (arg < 0 || arg >= MAXGROUPS)
					return(bytecopy(result, errorm, NSTRING * 2));
				    
				return(bytecopy(result, fixnull(grpmatch[arg]),
					 NSTRING * 2));
#else
				if (arg == 0)
					bytecopy(result, patmatch, NSTRING * 2);
				else
					return(bytecopy(result, errorm, NSTRING * 2));
				return(result);
#endif
		case UFGTCMD:	return(cmdstr(getcmd(), result));
		case UFGTKEY:	result[0] = tgetc();
				result[1] = 0;
				return(result);
		case UFIND:	return(strcpy(result, fixnull(getval(arg1))));
		case UFISNUM:	return(ltos(is_num(arg1)));
		case UFLEFT:	return(bytecopy(result, arg1, asc_int(arg2)));
		case UFLENGTH:	return(int_asc(strlen(arg1)));
		case UFLESS:	return(ltos(asc_int(arg1) < asc_int(arg2)));
		case UFLOWER:	return(mklower(arg1));
		case UFMID:	arg = asc_int(arg2);
				if (arg > strlen(arg1))
					arg = strlen(arg1);
				return(bytecopy(result, &arg1[arg-1],
					asc_int(arg3)));
		case UFMKCOL:	if ((arg = asc_int(arg1)) < 0 || arg >= NMARKS ||
				    curwp->w_markp[arg] == NULL)
				{
					mlwrite(TEXT11, arg);
					return (int_asc(-1));
				}
				return(int_asc(findcol(curwp->w_markp[arg], curwp->w_marko[arg])));
		case UFMKLINE:	if ((arg = asc_int(arg1)) < 0 || arg >= NMARKS ||
				    curwp->w_markp[arg] == NULL)
				{
					mlwrite(TEXT11, arg);
					return (int_asc(0));
				}
				return(long_asc(getlinenum(curbp, curwp->w_markp[arg])));
		case UFMOD:	if ((arg = asc_int(arg2)) != 0)
					return(int_asc(asc_int(arg1) % arg));
				else {
					mlwrite(TEXT245);
/*						"%%Division by Zero is illegal" */
					return(errorm);
				}
		case UFNEG:	return(int_asc(-asc_int(arg1)));
		case UFNOT:	return(ltos(stol(arg1) == FALSE));
		case UFOR:	return(ltos(stol(arg1) || stol(arg2)));
		case UFREVERSE: return(strrev(bytecopy(result, arg1, NSTRING * 2)));
		case UFRIGHT:	arg = asc_int(arg2);
				if (arg > strlen(arg1))
					arg = strlen(arg1);
				return(strcpy(result,
					&arg1[strlen(arg1) - arg]));
		case UFRND:	return(int_asc((int)(ernd() % (long)absv(asc_int(arg1))) + 1L));
		case UFSEQUAL:	return(ltos(strcmp(arg1, arg2) == 0));
		case UFSGREAT:	return(ltos(strcmp(arg1, arg2) > 0));
		case UFSINDEX:	return(int_asc(sindex(arg1, arg2)));
		case UFSLESS:	return(ltos(strcmp(arg1, arg2) < 0));
		case UFSLOWER:	return(setlower(arg1, arg2), "");
		case UFSUB:	return(int_asc(asc_int(arg1) - asc_int(arg2)));
		case UFSUPPER:	return(setupper(arg1, arg2), "");
		case UFTIMES:	return(int_asc(asc_int(arg1) * asc_int(arg2)));
		case UFTRIM:	return(trimstr(arg1));
		case UFTRUTH:	return(ltos(asc_int(arg1) == 42));
		case UFUPPER:	return(mkupper(arg1));
		case UFXLATE:	return(xlat(arg1, arg2, arg3));
	}

	meexit(-11);	/* never should get here */
}

char *PASCAL NEAR gtusr(vname)	/* look up a user var's value */

char *vname;		/* name of user variable to fetch */

{
	register int vnum;	/* ordinal number of user var */
	register char *vptr;	/* temp pointer to function value */
	register UTABLE *ut;	/* ptr to the current variable table */

	/* limit comparisons to significant length */
	if (strlen(vname) >= NVSIZE)	/* "%" counts, but is not passed */
		vname[NVSIZE] = '\0';
	
	/* scan through each user variable table starting with the
	   most local and going to the global table */
	ut = uv_head;
	while (ut) {

		/* scan this table looking for the user var name */
		for (vnum = 0; vnum < ut->size; vnum++) {

			/* out of entries? */
			if (ut->uv[vnum].u_name[0] == 0)
				goto next_ut;

			/* is this the one? */
			if (strcmp(vname, ut->uv[vnum].u_name) == 0) {

				/* return its value..... */
				vptr = ut->uv[vnum].u_value;
				if (vptr)
					return(vptr);
				else
					return(errorm);
			}
		}

next_ut:	ut = ut->next;
	}

	/* return errorm if we run off the end */
	return(errorm);
}

char *PASCAL NEAR funval(i)

int i;

{
	return(funcs[i].f_name);
}

char *PASCAL NEAR envval(i)

int i;

{
	return(envars[i]);
}

PASCAL NEAR binary(key, tval, tlength, klength)

char *key;		/* key string to look for */
char *(PASCAL NEAR *tval)();	/* ptr to function to fetch table value with */
int tlength;		/* length of table to search */
int klength;		/* maximum length of string to compare */

{
	int l, u;	/* lower and upper limits of binary search */
	int i;		/* current search index */
	int cresult;	/* result of comparison */

	/* set current search limit as entire list */
	l = 0;
	u = tlength - 1;

	/* get the midpoint! */
	while (u >= l) {
		i = (l + u) >> 1;

		/* do the comparison */
		cresult = strncmp(key, (*tval)(i), klength);
		if (cresult == 0)
			return(i);
		if (cresult < 0)
			u = i - 1;
		else
			l = i + 1;
	}
	return(-1);
}

char *PASCAL NEAR gtenv(vname)

char *vname;		/* name of environment variable to retrieve */

{
	register int vnum;	/* ordinal number of var refrenced */
	static char result[2 * NSTRING];	/* string result */

	/* scan the list, looking for the referenced name */
	vnum = binary(vname, envval, NEVARS, NVSIZE);

	/* return errorm on a bad reference */
	if (vnum == -1)
		return(errorm);

	/* otherwise, fetch the appropriate value */
	switch (vnum) {
		case EVABBELL:	return(ltos(ab_bell));
		case EVABCAP:	return(ltos(ab_cap));
		case EVABQUICK:	return(ltos(ab_quick));
		case EVACOUNT:	return(int_asc(gacount));
		case EVASAVE:	return(int_asc(gasave));
		case EVBUFHOOK: return(fixnull(getfname(&bufhook)));
		case EVCBFLAGS: return(int_asc(curbp->b_flag));
		case EVCBUFNAME:return(curbp->b_bname);
		case EVCFNAME:	return(curbp->b_fname);
		case EVCMDHK:	return(fixnull(getfname(&cmdhook)));
		case EVCMODE:	return(int_asc(curbp->b_mode));
		case EVCURCHAR:
			return(lused(curwp->w_dotp) ==
					curwp->w_doto ? int_asc('\r') :
				int_asc(lgetc(curwp->w_dotp, curwp->w_doto)));
		case EVCURCOL:	return(int_asc(getccol(FALSE)));
		case EVCURLINE: return(long_asc(getlinenum(curbp, curwp->w_dotp)));
		case EVCURWIDTH:return(int_asc(term.t_ncol));
		case EVCURWIND: return(int_asc(getcwnum()));
		case EVCWLINE:	return(int_asc(getwpos()));
		case EVDEBUG:	return(ltos(macbug));
		case EVDESKCLR: return(cname[deskcolor]);
		case EVDIAGFLAG:return(ltos(diagflag));
		case EVDISCMD:	return(ltos(discmd));
		case EVDISINP:	return(ltos(disinp));
		case EVDISPHIGH:return(ltos(disphigh));
		case EVDISPUNDO:return(ltos(dispundo));
		case EVEXBHOOK: return(fixnull(getfname(&exbhook)));
		case EVEXITHOOK:return(fixnull(getfname(&exithook)));
		case EVFCOL:	return(int_asc(curwp->w_fcol));
		case EVFILLCOL: return(int_asc(fillcol));
		case EVFLICKER: return(ltos(flickcode));
		case EVFMTLEAD: return(fmtlead);
		case EVGFLAGS:	return(int_asc(gflags));
		case EVGMODE:	return(int_asc(gmode));
		case EVHARDTAB: return(int_asc(tabsize));
		case EVHILITE:	return(int_asc(hilite));
		case EVHJUMP:	return(int_asc(hjump));
		case EVHSCRLBAR: return(ltos(hscrollbar));
		case EVHSCROLL: return(ltos(hscroll));
		case EVISTERM:	return(cmdstr(isterm, result));
		case EVKILL:	return(getkill());
		case EVLANG:	return(LANGUAGE);
		case EVLASTKEY: return(int_asc(lastkey));
		case EVLASTMESG:return(lastmesg);
		case EVLINE:	return(getctext(result));
		case EVLTERM:	return(lterm);
		case EVLWIDTH:	return(int_asc(lused(curwp->w_dotp)));
		case EVMATCH:	return(fixnull(patmatch));
		case EVMMOVE:	return(int_asc(mouse_move));
		case EVMODEFLAG:return(ltos(modeflag));
		case EVMSFLAG:	return(ltos(mouseflag));
		case EVNEWSCRN:	return(ltos(newscreenflag));
		case EVNUMWIND: return(int_asc(gettwnum()));
		case EVORGCOL:	return(int_asc(term.t_colorg));
		case EVORGROW:	return(int_asc(term.t_roworg));
		case EVOS:	return(os);
		case EVOVERLAP: return(int_asc(overlap));
		case EVPAGELEN: return(int_asc(term.t_nrow + 1));
		case EVPALETTE: return(palstr);
		case EVPARALEAD:return(paralead);
		case EVPENDING:
#if	TYPEAH || WINDOW_MSWIN
				return(ltos(typahead()));
#else
				return(falsem);
#endif
		case EVPOPFLAG: return(ltos(popflag));
		case EVPOPWAIT: return(ltos(popwait));
		case EVPOSFLAG: return(ltos(posflag));
		case EVPROGNAME:return(PROGNAME);
		case EVRAM:	return(int_asc((int)(envram / 1024l)));
		case EVREADHK:	return(fixnull(getfname(&readhook)));
		case EVREGION:	return(getreg(result));
		case EVREPLACE: return((char *)rpat);
		case EVRVAL:	return(rval);
		case EVSCRNAME: return(first_screen->s_screen_name);
		case EVSEARCH:	return((char *)pat);
		case EVSEARCHPNT:	return(int_asc(searchtype));
		case EVSEED:	return(int_asc((int)seed));
		case EVSOFTTAB: return(int_asc(stabsize));
		case EVSRES:	return(sres);
		case EVSSAVE:	return(ltos(ssave));
		case EVSSCROLL: return(ltos(sscroll));
		case EVSTATUS:	return(ltos(cmdstatus));
		case EVSTERM:	return(cmdstr(sterm, result));
		case EVTARGET:	saveflag = lastflag;
				return(int_asc(curgoal));
		case EVTIME:	return(timeset());
		case EVTIMEFLAG: return(ltos(timeflag));
		case EVTPAUSE:	return(int_asc(term.t_pause));
		case EVUNDOFLAG: return(ltos(undoflag));
		case EVVERSION: return(VERSION);
		case EVVSCRLBAR: return(ltos(vscrollbar));
		case EVWCHARS:	return(getwlist(result));
		case EVWLINE:	return(int_asc(curwp->w_ntrows));
		case EVWRAPHK:	return(fixnull(getfname(&wraphook)));
		case EVWRITEHK: return(fixnull(getfname(&writehook)));
		case EVXPOS:	return(int_asc(xpos));
		case EVYANKFLAG: return(ltos(yankflag));
		case EVYPOS:	return(int_asc(ypos));
	}
	meexit(-12);	/* again, we should never get here */
}

char *PASCAL NEAR fixnull(s)	/* Don't return NULL pointers! */

char *s;

{
	if (s == NULL)
		return("");
	else
		return(s);
}

/* return some of the contents of the kill buffer */

char *PASCAL NEAR getkill()

{
	register int size;	/* max number of chars left to return */
	register char *sp;	/* ptr into KILL block data chunk */
	register char *vp;	/* ptr into return value */
	KILL *kptr;		/* ptr to the current KILL block */
	int counter;		/* index into data chunk */
	static char value[NSTRING];	/* temp buffer for value */

	/* no kill buffer....just a null string */
	if (kbufh[kill_index] == (KILL *)NULL) {
		value[0] = 0;
		return(value);
	}

	/* set up the output buffer */
	vp = value;
	size = NSTRING - 1;

	/* backed up characters? */
	if (kskip[kill_index] > 0) {
		kptr = kbufh[kill_index];
		sp = &(kptr->d_chunk[kskip[kill_index]]);
		counter = kskip[kill_index];
		while (counter++ < KBLOCK) {
			*vp++ = *sp++;
			if (--size == 0) {
				*vp = 0;
				return(value);
			}
		}
		kptr = kptr->d_next;
	} else {
		kptr = kbufh[kill_index];
	}

	if (kptr != (KILL *)NULL) {
		while (kptr != kbufp[kill_index]) {
			sp = kptr->d_chunk;
			for (counter = 0; counter < KBLOCK; counter++) {
				*vp++ = *sp++;
				if (--size == 0) {
					*vp = 0;
					return(value);
				}
			}
			kptr = kptr->d_next;
		}
		counter = kused[kill_index];
		sp = kptr->d_chunk;
		while (counter--) {
			*vp++ = *sp++;
			if (--size == 0) {
				*vp = 0;
				return(value);
			}
		}
	}
        
	/* and return the constructed value */
	*vp = 0;
	return(value);
}

char *PASCAL NEAR trimstr(s)	/* trim whitespace off the end of a string */

char *s;	/* string to trim */

{
	char *sp;	/* backward index */

	sp = s + strlen(s) - 1;
	while ((sp >= s) && (*sp == ' ' || *sp == '\t'))
		--sp;
	*(sp+1) = 0;
	return(s);
}

int PASCAL NEAR setvar(f, n)		/* set a variable */

int f;		/* default flag */
int n;		/* numeric arg (can overide prompted value) */

{
	register int status;	/* status return */
	VDESC vd;		/* variable num/type */
	char var[NVSIZE+1];	/* name of variable to fetch */
	char value[NSTRING];	/* value to set variable to */

	/* first get the variable to set.. */
	if (clexec == FALSE) {
		status = mlreply(TEXT51, &var[0], NVSIZE+1);
/*				 "Variable to set: " */
		if (status != TRUE)
			return(status);
	} else {	/* macro line argument */
		/* grab token and skip it */
		execstr = token(execstr, var, NVSIZE + 1);
	}

	/* check the legality and find the var */
	findvar(var, &vd, NVSIZE + 1, VT_GLOBAL);
        
	/* if its not legal....bitch */
	if (vd.v_type == -1) {
		mlwrite(TEXT52, var);
/*			"%%No such variable as '%s'" */
		return(FALSE);
	}

	/* get the value for that variable */
	if (f == TRUE)
		strcpy(value, int_asc(n));
	else {
		status = mlreply(TEXT53, &value[0], NSTRING);
/*				 "Value: " */
		if (status == ABORT)
			return(status);
	}

	/* and set the appropriate value */
	status = svar(&vd, value);

	/* if $debug == TRUE, every assignment will echo a statment to
	   that effect here. */
        
	if (macbug && (strcmp(var, "%track") != 0)) {
		strcpy(outline, "(((");

		strcat(outline, var);
		strcat(outline, " <- ");

		/* and lastly the value we tried to assign */
		strcat(outline, value);
		strcat(outline, ")))");

		/* write out the debug line */
		mlforce(outline);
		update(TRUE);

		/* and get the keystroke to hold the output */
		if (get_key() == abortc) {
			mlforce(TEXT54);
/*				"[Macro aborted]" */
			status = FALSE;
		}
	}

	/* and return it */
	return(status);
}

int PASCAL NEAR global_var(f, n)	/* declare a global variable */

int f;		/* default flag */
int n;		/* numeric arg (ignored here) */

{
	register int status;	/* status return */
	VDESC vd;		/* variable num/type */
	char var[NVSIZE+1];	/* name of variable to fetch */

	/* first get the variable to set.. */
	if (clexec == FALSE) {
		status = mlreply(TEXT249, &var[0], NVSIZE+1);
/*				 "Global variable to declare: " */
		if (status != TRUE)
			return(status);
	} else {	/* macro line argument */
		/* grab token and skip it */
		execstr = token(execstr, var, NVSIZE + 1);
	}

	/* check the legality and find the var */
	findvar(var, &vd, NVSIZE + 1, VT_GLOBAL);
        
	/* if its not legal....bitch */
	if (vd.v_type == -1) {
		mlwrite(TEXT52, var);
/*			"%%No such variable as '%s'" */
		return(FALSE);
	}

	/* and set the appropriate value */
	status = svar(&vd, "");

	/* if $debug == TRUE, every assignment will echo a statment to
	   that effect here. */
        
	if (macbug && (strcmp(var, "%track") != 0)) {
		strcpy(outline, "(((Globally declare ");

		strcat(outline, var);
		strcat(outline, ")))");

		/* write out the debug line */
		mlforce(outline);
		update(TRUE);

		/* and get the keystroke to hold the output */
		if (get_key() == abortc) {
			mlforce(TEXT54);
/*				"[Macro aborted]" */
			status = FALSE;
		}
	}

	/* and return it */
	return(status);
}

int PASCAL NEAR local_var(f, n)	/* declare a local variable */

int f;		/* default flag */
int n;		/* numeric arg (ignored here) */

{
	register int status;	/* status return */
	VDESC vd;		/* variable num/type */
	char var[NVSIZE+1];	/* name of variable to fetch */

	/* first get the variable to set.. */
	if (clexec == FALSE) {
		status = mlreply(TEXT250, &var[0], NVSIZE+1);
/*				 "Local variable to declare: " */
		if (status != TRUE)
			return(status);
	} else {	/* macro line argument */
		/* grab token and skip it */
		execstr = token(execstr, var, NVSIZE + 1);
	}

	/* check the legality and find the var */
	findvar(var, &vd, NVSIZE + 1, VT_LOCAL);
        
	/* if its not legal....bitch */
	if (vd.v_type == -1) {
		mlwrite(TEXT52, var);
/*			"%%No such variable as '%s'" */
		return(FALSE);
	}

	/* and set the appropriate value */
	status = svar(&vd, "");

	/* if $debug == TRUE, every assignment will echo a statment to
	   that effect here. */
        
	if (macbug && (strcmp(var, "%track") != 0)) {
		strcpy(outline, "(((Locally declare ");

		strcat(outline, var);
		strcat(outline, ")))");

		/* write out the debug line */
		mlforce(outline);
		update(TRUE);

		/* and get the keystroke to hold the output */
		if (get_key() == abortc) {
			mlforce(TEXT54);
/*				"[Macro aborted]" */
			status = FALSE;
		}
	}

	/* and return it */
	return(status);
}

/* find a variables type and name */

VOID PASCAL NEAR findvar(var, vd, size, scope)

char *var;	/* name of var to get */
VDESC *vd;	/* structure to hold type and ptr */
int size;	/* size of var array */
int scope;	/* intended scope of any created user variables */

{
	register int vnum;	/* subscript in varable arrays */
	register int vtype;	/* type to return */
	register UTABLE *vut;	/* user var table to search */

fvar:	vtype = -1;
	vut = uv_head;

	switch (var[0]) {

		case '$': /* check for legal enviromnent var */
			if ((vnum = binary(&var[1], envval, NEVARS, NVSIZE)) != -1)
				vtype = TKENV;
			break;

		case '%': /* check for existing legal user variable */
			while (vut) {
				for (vnum = 0; vnum < vut->size; vnum++)
					if (strcmp(&var[1],
					    vut->uv[vnum].u_name) == 0) {
						vtype = TKVAR;
						goto retvar;
					}
				vut = vut->next;
				if (scope == VT_LOCAL)
					break;
			}

			/* if we should not define one.... */
			if (scope == VT_NONE)
				break;

			/* scope it as requested */
			if (scope == VT_LOCAL)
				vut = uv_head;
			else
				vut = uv_global;

			/* no room left in requested user var table? */
			if (vnum < vut->size)
				break;

			/* create a new variable */
			for (vnum = 0; vnum < vut->size; vnum++)
				if (vut->uv[vnum].u_name[0] == 0) {
					vtype = TKVAR;
					memset((char *)&vut->uv[vnum].u_name[0], '\0', NVSIZE);
					strncpy(vut->uv[vnum].u_name, &var[1], NVSIZE);
					vut->uv[vnum].u_value = NULL;
					break;
				}
			break;

		case '&':	/* indirect operator? */
			var[4] = 0;
			if (strcmp(&var[1], "ind") == 0) {
				/* grab token, and eval it */
				execstr = token(execstr, var, size);
				strcpy(var, fixnull(getval(var)));
				goto fvar;
			}
	}

	/* return the results */
retvar:	vd->v_num = vnum;
	vd->v_type = vtype;
	vd->v_ut = vut;
	return;
}

int PASCAL NEAR svar(var, value)	/* set a variable */

VDESC *var;	/* variable to set */
char *value;	/* value to set to */

{
	register int vnum;	/* ordinal number of var refrenced */
	register int vtype;	/* type of variable to set */
	register UTABLE *vut;	/* user table pointer */
	register int status;	/* status return */
	register int c; 	/* translated character */
	register char *sp;	/* scratch string pointer */

	/* simplify the vd structure (we are gonna look at it a lot) */
	vnum = var->v_num;
	vtype = var->v_type;
	vut = var->v_ut;

	/* and set the appropriate value */
	status = TRUE;
	switch (vtype) {
	case TKVAR: /* set a user variable */
		if (vut->uv[vnum].u_value != NULL)
			free(vut->uv[vnum].u_value);
		sp = room(strlen(value) + 1);
		if (sp == NULL)
			return(FALSE);
		strcpy(sp, value);
		vut->uv[vnum].u_value = sp;

		/* setting a variable to error stops macro execution */
		if (strcmp(value, errorm) == 0)
			status = FALSE;

		break;

	case TKENV: /* set an environment variable */
		status = TRUE;	/* by default */

		switch (vnum) {
		case EVABBELL:	ab_bell = stol(value);
				break;
		case EVABCAP:	ab_cap = stol(value);
				break;
		case EVABQUICK:	ab_quick = stol(value);
				break;
		case EVACOUNT:	gacount = asc_int(value);
				break;
		case EVASAVE:	gasave = asc_int(value);
				break;
		case EVBUFHOOK: set_key(&bufhook, value);
				break;
		case EVCBFLAGS: c = asc_int(value);
				curbp->b_flag = (curbp->b_flag & ~(BFCHG|BFINVS))
					| (c & (BFCHG|BFINVS));
				if ((c & BFCHG) == BFCHG)
					lchange(WFMODE);
				break;
		case EVCBUFNAME:strcpy(curbp->b_bname, value);
				curwp->w_flag |= WFMODE;
				break;
		case EVCFNAME:	strcpy(curbp->b_fname, value);
#if	WINDOW_MSWIN
				fullpathname(curbp->b_fname, NFILEN);
#endif
				curwp->w_flag |= WFMODE;
				break;
		case EVCMDHK:	set_key(&cmdhook, value);
				break;
		case EVCMODE:	curbp->b_mode = asc_int(value);
				curwp->w_flag |= WFMODE;
				break;
		case EVCURCHAR: ldelete(1L, FALSE);	/* delete 1 char */
				c = asc_int(value);
				if (c == '\r')
					lnewline();
				else
					linsert(1, (char)c);
				backchar(FALSE, 1);
				break;
		case EVCURCOL:	status = setccol(asc_int(value));
				break;
		case EVCURLINE: status = gotoline(TRUE, asc_int(value));
				break;
		case EVCURWIDTH:status = newwidth(TRUE, asc_int(value));
				break;
		case EVCURWIND: nextwind(TRUE, asc_int(value));
				break;
		case EVCWLINE:	status = forwline(TRUE,
						asc_int(value) - getwpos());
				break;
		case EVDEBUG:	macbug = stol(value);
				break;
		case EVDESKCLR: c = lookup_color(mkupper(value));
				if (c != -1) {
					deskcolor = c;
#if	WINDOW_TEXT
					refresh_screen(first_screen);
#endif
				}
				break;
		case EVDIAGFLAG:diagflag = stol(value);
				break;
		case EVDISCMD:	discmd = stol(value);
				break;
		case EVDISINP:	disinp = stol(value);
				break;
		case EVDISPHIGH:
				c = disphigh;
				disphigh = stol(value);
				if (c != disphigh)
					upwind();
				break;
		case EVDISPUNDO:
				dispundo = stol(value);
				break;
		case EVEXBHOOK: set_key(&exbhook, value);
				break;
		case EVEXITHOOK:set_key(&exithook, value);
				break;
		case EVFCOL:	curwp->w_fcol = asc_int(value);
				if (curwp->w_fcol < 0)
					curwp->w_fcol = 0;
				curwp->w_flag |= WFHARD | WFMODE;
				break;
		case EVFILLCOL: fillcol = asc_int(value);
				break;
		case EVFLICKER: flickcode = stol(value);
				break;
		case EVFMTLEAD: bytecopy(fmtlead, value, NSTRING);
				break;
		case EVGFLAGS:	gflags = asc_int(value);
				break;
		case EVGMODE:	gmode = asc_int(value);
				break;
		case EVHARDTAB: if ((c = asc_int(value)) >= 0)
				{
					tabsize = c;
					upwind();
				}
				break;
		case EVHILITE:	hilite = asc_int(value);
				if (hilite > NMARKS)
					hilite = 255;
				break;
		case EVHJUMP:	hjump = asc_int(value);
				if (hjump < 1)
					hjump = 1;
				if (hjump > term.t_ncol - 1)
					hjump = term.t_ncol - 1;
				break;
		case EVHSCRLBAR: hscrollbar = stol(value);
				break;
		case EVHSCROLL: hscroll = stol(value);
				lbound = 0;
				break;
		case EVISTERM:	isterm = stock(value);
				break;
		case EVKILL:	break;
		case EVLANG:	break;
		case EVLASTKEY: lastkey = asc_int(value);
				break;
		case EVLASTMESG:strcpy(lastmesg, value);
				break;
		case EVLINE:	putctext(value);
				break;
		case EVLTERM:	bytecopy(lterm, value, NSTRING);
				break;
		case EVLWIDTH:	break;
		case EVMATCH:	break;
		case EVMMOVE:	mouse_move = asc_int(value);
				if (mouse_move < 0) mouse_move = 0;
				if (mouse_move > 2) mouse_move = 2;
				break;
		case EVMODEFLAG:modeflag = stol(value);
				upwind();
				break;
		case EVMSFLAG:	mouseflag = stol(value);
				break;
		case EVNEWSCRN:	newscreenflag = stol(value);
				break;
		case EVNUMWIND: break;
		case EVORGCOL:	status = new_col_org(TRUE, asc_int(value));
				break;
		case EVORGROW:	status = new_row_org(TRUE, asc_int(value));
				break;
		case EVOS:	break;
		case EVOVERLAP: overlap = asc_int(value);
				break;
		case EVPAGELEN: status = newsize(TRUE, asc_int(value));
				break;
		case EVPALETTE: bytecopy(palstr, value, 48);
				spal(palstr);
				break;
		case EVPARALEAD:bytecopy(paralead, value, NSTRING);
				break;
		case EVPENDING: break;
		case EVPOPFLAG: popflag = stol(value);
				break;
		case EVPOPWAIT: popwait = stol(value);
				break;
		case EVPOSFLAG: posflag = stol(value);
				upmode();
				break;
		case EVPROGNAME:break;
		case EVRAM:	break;
		case EVREADHK:	set_key(&readhook, value);
				break;
		case EVREGION:	break;
		case EVREPLACE: strcpy((char *)rpat, value);
#if	MAGIC
				rmcclear();
#endif 
				break;
		case EVRVAL:	strcpy(rval, value);
				break;
		case EVSCRNAME: select_screen(lookup_screen(value), TRUE);
				break;
		case EVSEARCH:	strcpy((char *)pat, value);
				setjtable(); /* Set up fast search arrays  */
#if	MAGIC
				mcclear();
#endif
				break;
		case EVSEARCHPNT:	searchtype = asc_int(value);
				if (searchtype < SRNORM  || searchtype > SREND)
					searchtype = SRNORM;
				break;
		case EVSEED:	seed = (long)abs(asc_int(value));
				break;
		case EVSOFTTAB: stabsize = asc_int(value);
				upwind();
				break;
		case EVSRES:	status = TTrez(value);
				break;
		case EVSSAVE:	ssave = stol(value);
				break;
		case EVSSCROLL: sscroll = stol(value);
				break;
		case EVSTATUS:	cmdstatus = stol(value);
				break;
		case EVSTERM:	sterm = stock(value);
				break;
		case EVTARGET:	curgoal = asc_int(value);
				thisflag = saveflag;
				break;
		case EVTIME:	break;
		case EVTIMEFLAG: timeflag = stol(value);
				upmode();
				break;
		case EVTPAUSE:	term.t_pause = asc_int(value);
				break;
		case EVUNDOFLAG:if (undoflag != stol(value))
					undo_dump();
				undoflag = stol(value);
				break;
		case EVVERSION: break;
		case EVVSCRLBAR: vscrollbar = stol(value);
				break;
		case EVWCHARS:	setwlist(value);
				break;
		case EVWLINE:	status = resize(TRUE, asc_int(value));
				break;
		case EVWRAPHK:	set_key(&wraphook, value);
				break;
		case EVWRITEHK: set_key(&writehook, value);
				break;
		case EVXPOS:	xpos = asc_int(value);
				break;
		case EVYANKFLAG:	yankflag = stol(value);
				break;
		case EVYPOS:	ypos = asc_int(value);
				break;
		}
		break;
	}
	return(status);
}

/*	asc_int:	ascii string to integer......This is too
		inconsistant to use the system's	*/

int PASCAL NEAR asc_int(st)

char *st;

{
	int result;	/* resulting number */
	int sign;	/* sign of resulting number */
	char c; 	/* current char being examined */

	result = 0;
	sign = 1;

	/* skip preceding whitespace */
	while (*st == ' ' || *st == '\t')
		++st;

	/* check for sign */
	if (*st == '-') {
		sign = -1;
		++st;
	}
	if (*st == '+')
		++st;

	/* scan digits, build value */
	while ((c = *st++))
		if (c >= '0' && c <= '9')
			result = result * 10 + c - '0';
		else
			break;

	return(result * sign);
}

/*	int_asc:	integer to ascii string.......... This is too
			inconsistant to use the system's	*/

char *PASCAL NEAR int_asc(i)

int i;	/* integer to translate to a string */

{
	register int digit;		/* current digit being used */
	register char *sp;		/* pointer into result */
	register int sign;		/* sign of resulting number */
	static char result[INTWIDTH+1]; /* resulting string */

	/* this is a special case */
	if (i == -32768) {
		strcpy(result, "-32768");
		return(result);
	}

	/* record the sign...*/
	sign = 1;
	if (i < 0) {
		sign = -1;
		i = -i;
	}

	/* and build the string (backwards!) */
	sp = result + INTWIDTH;
	*sp = 0;
	do {
		digit = i % 10;
		*(--sp) = '0' + digit;	/* and install the new digit */
		i = i / 10;
	} while (i);

	/* and fix the sign */
	if (sign == -1) {
		*(--sp) = '-';	/* and install the minus sign */
	}

	return(sp);
}

/*	long_asc:	long to ascii string.......... This is too
			inconsistant to use the system's	*/

char *PASCAL NEAR long_asc(num)

long num;	/* integer to translate to a string */

{
	register int digit;		/* current digit being used */
	register char *sp;		/* pointer into result */
	register int sign;		/* sign of resulting number */
	static char result[LONGWIDTH+1]; /* resulting string */

	/* record the sign...*/
	sign = 1;
	if (num < 0L) {
		sign = -1;
		num = -num;
	}

	/* and build the string (backwards!) */
	sp = result + LONGWIDTH;
	*sp = 0;
	do {
		digit = num % 10;
		*(--sp) = '0' + digit;	/* and install the new digit */
		num = num / 10L;
	} while (num);

	/* and fix the sign */
	if (sign == -1) {
		*(--sp) = '-';	/* and install the minus sign */
	}

	return(sp);
}

int PASCAL NEAR gettyp(token)	/* find the type of a passed token */

char *token;	/* token to analyze */

{
	register char c;	/* first char in token */

	/* grab the first char (this is all we need) */
	c = *token;

	/* no blanks!!! */
	if (c == 0)
		return(TKNUL);

	/* a numeric literal? */
	if (c >= '0' && c <= '9')
		return(TKLIT);

	switch (c) {
		case '"':	return(TKSTR);

		case '!':	return(TKDIR);
		case '@':	return(TKARG);
		case '#':	return(TKBUF);
		case '$':	return(TKENV);
		case '%':	return(TKVAR);
		case '&':	return(TKFUN);
		case '*':	return(TKLBL);

		default:	return(TKCMD);
	}
}

char *PASCAL NEAR getval(token) /* find the value of a token */

char *token;		/* token to evaluate */

{
	register int status;	/* error return */
	register BUFFER *bp;	/* temp buffer pointer */
	register int blen;	/* length of buffer argument */
	static char buf[NSTRING];/* string buffer for some returns */

	switch (gettyp(token)) {
		case TKNUL:	return("");

		case TKARG:	/* interactive argument */
				strcpy(token, fixnull(getval(&token[1])));
				mlwrite("%s", token);
				status = getstring(buf, NSTRING, ctoec(RETCHAR));
				if (status == ABORT)
					return(NULL);
				return(buf);

		case TKBUF:	/* buffer contents fetch */

				/* grab the right buffer */
				strcpy(token, fixnull(getval(&token[1])));
				bp = bfind(token, FALSE, 0);
				if (bp == NULL)
					return(NULL);
	        
				/* if the buffer is displayed, get the window
				   vars instead of the buffer vars */
				if (bp->b_nwnd > 0) {
					curbp->b_dotp = curwp->w_dotp;
					curbp->b_doto = curwp->w_doto;
				}

				/* if we are at the end, return <END> */
				if (bp->b_linep == bp->b_dotp)
					return("<END>");
	        
				/* grab the line as an argument */
				blen = lused(bp->b_dotp) - bp->b_doto;
				if (blen > NSTRING)
					blen = NSTRING;
				bytecopy(buf, ltext(bp->b_dotp) + bp->b_doto,
					blen);
				buf[blen] = 0;
	        
				/* and step the buffer's line ptr ahead a line */
				bp->b_dotp = lforw(bp->b_dotp);
				bp->b_doto = 0;

				/* if displayed buffer, reset window ptr vars*/
				if (bp->b_nwnd > 0) {
					curwp->w_dotp = curbp->b_dotp;
					curwp->w_doto = 0;
					curwp->w_flag |= WFMOVE;
				}

				/* and return the spoils */
				return(buf);	        

		case TKVAR:	return(gtusr(token+1));
		case TKENV:	return(gtenv(token+1));
		case TKFUN:	return(gtfun(token+1));
		case TKDIR:	return(NULL);
		case TKLBL:	return(NULL);
		case TKLIT:	return(token);
		case TKSTR:	return(token+1);
		case TKCMD:	return(token);
	}
}

int PASCAL NEAR stol(val)	/* convert a string to a numeric logical */

char *val;	/* value to check for stol */

{
	/* check for logical values */
	if (val[0] == 'F')
		return(FALSE);
	if (val[0] == 'T')
		return(TRUE);

	/* check for numeric truth (!= 0) */
	return((asc_int(val) != 0));
}

char *PASCAL NEAR ltos(val)	/* numeric logical to string logical */

int val;	/* value to translate */

{
	if (val)
		return(truem);
	else
		return(falsem);
}

char *PASCAL NEAR mkupper(str)	/* make a string upper case */

char *str;		/* string to upper case */

{
	char *sp;

	sp = str;
	while (*sp)
		uppercase((unsigned char *)sp++);
	return(str);
}

char *PASCAL NEAR mklower(str)	/* make a string lower case */

char *str;		/* string to lower case */

{
	char *sp;

	sp = str;
	while (*sp)
		lowercase((unsigned char *)sp++);
	return(str);
}

int PASCAL NEAR absv(x) /* take the absolute value of an integer */

int x;

{
	return(x < 0 ? -x : x);
}

long PASCAL NEAR ernd()	/* returns a random integer */

/* This function implements the "minimal standard" RNG
   from the paper "RNGs: Good Ones are Hard to Find" by Park and
   Miller, CACM, Volume 31, Number 10, October 1988. */

{
	long int a=16807L, m=2147483647L, q=127773L, r=2836L;
	long lo, hi, test;

	hi = seed / q;
	lo = seed % q;
	test = a * lo - r * hi;
	seed = (test > 0) ? test : test + m;
	return(seed);
}

int PASCAL NEAR sindex(source, pattern) /* find pattern within source */

char *source;	/* source string to search */
char *pattern;	/* string to look for */

{
	char *sp;	/* ptr to current position to scan */
	char *csp;	/* ptr to source string during comparison */
	char *cp;	/* ptr to place to check for equality */

	/* scanning through the source string */
	sp = source;
	while (*sp) {
		/* scan through the pattern */
		cp = pattern;
		csp = sp;
		while (*cp) {
			if (!eq(*cp, *csp))
				break;
			++cp;
			++csp;
		}

		/* was it a match? */
		if (*cp == 0)
			return((int)(sp - source) + 1);
		++sp;
	}

	/* no match at all.. */
	return(0);
}

/*	Filter a string through a translation table	*/

char *PASCAL NEAR xlat(source, lookup, trans)

char *source;	/* string to filter */
char *lookup;	/* characters to translate */
char *trans;	/* resulting translated characters */

{
	register char *sp;	/* pointer into source table */
	register char *lp;	/* pointer into lookup table */
	register char *rp;	/* pointer into result */
	static char result[NSTRING];	/* temporary result */

	/* scan source string */
	sp = source;
	rp = result;
	while (*sp) {
		/* scan lookup table for a match */
		lp = lookup;
		while (*lp) {
			if (*sp == *lp) {
				*rp++ = trans[lp - lookup];
				goto xnext;
			}
			++lp;
		}

		/* no match, copy in the source char untranslated */
		*rp++ = *sp;

xnext:		++sp;
	}

	/* terminate and return the result */
	*rp = 0;
	return(result);
}

/*	setwlist:	Set an alternative list of character to be
			considered "in a word */

PASCAL NEAR setwlist(wclist)

char *wclist;	/* list of characters to consider "in a word" */

{
	register int index;

	/* if we are turning this facility off, just flag so */
	if (wclist == NULL || *wclist == 0) {
		wlflag = FALSE;
		return;
	}

	/* first clear the table */
	for (index = 0; index < 256; index++)
		wordlist[index] = FALSE;

	/* and for each character in the new value, set that element
	   of the word character list */
	while (*wclist)
		wordlist[(unsigned char)(*wclist++)] = TRUE;	/* ep */
	wlflag = TRUE;
	return;
}

/*	getwlist:	place in a buffer a list of characters
			considered "in a word"			*/

char *PASCAL NEAR getwlist(buf)

char *buf;	/* buffer to place list of characters */

{
	register int index;
	register char *sp;

	/* if we are defaulting to a standard word char list... */
	if (wlflag == FALSE)
		return("");

	/* build the string of characters in the return buffer */
	sp = buf;
	for (index = 0; index < 256; index++)
		if (wordlist[index])
			*sp++ = index;
	*sp = 0;
	return(buf);
}

/*	is_num: ascii string is integer......This is too
		inconsistant to use the system's	*/
 
int PASCAL NEAR is_num(st)
 
char *st;
 
{
	int period_flag;	/* have we seen a period yet? */

	/* skip preceding whitespace */
	while (*st == ' ' || *st == '\t')
		++st;
 
	/* check for sign */
	if ((*st == '-') || (*st == '+'))
		++st;
 
	/* scan digits */
	period_flag = FALSE;
	while ((*st >= '0') && (*st <= '9') ||
	       (*st == '.' && period_flag == FALSE)) {
		if (*st == '.')
			period_flag = TRUE;
		st++;
	}
 
	/* scan rest of line for just white space */
	while (*st) {
		if ((*st != '\t') && (*st != ' '))
			return(FALSE);
		st++;
	}
	return(TRUE);
}

int PASCAL NEAR dispvar(f, n)		/* display a variable's value */

int f;		/* default flag */
int n;		/* numeric arg (can overide prompted value) */

{
	register int status;	/* status return */
	VDESC vd;		/* variable num/type */
	char var[NVSIZE+1];	/* name of variable to fetch */

	/* first get the variable to display.. */
	if (clexec == FALSE) {
		status = mlreply(TEXT55, &var[0], NVSIZE+1);
/*				 "Variable to display: " */
		if (status != TRUE)
			return(status);
	} else {	/* macro line argument */
		/* grab token and skip it */
		execstr = token(execstr, var, NVSIZE + 1);
	}

	/* check the legality and find the var */
	findvar(var, &vd, NVSIZE + 1, VT_NONE);
        
	/* if its not legal....bitch */
	if (vd.v_type == -1) {
		mlwrite(TEXT52, var);
/*			"%%No such variable as '%s'" */
		return(FALSE);
	}

	/* and display the value */
	strcpy(outline, var);
	strcat(outline, " = ");

	/* and lastly the current value */
	strcat(outline, fixnull(getval(var)));

	/* write out the result */
	mlforce(outline);
	update(TRUE);

	/* and return */
	return(TRUE);
}

/*	describe-variables	Bring up a fake buffer and list the contents
				of all the environment variables
*/

PASCAL NEAR desvars(f, n)

int f,n;	/* prefix flag and argument */

{
	register BUFFER *varbuf;/* buffer to put variable list into */
	register int uindex;	/* index into uvar table */
	UTABLE *ut;		/* user variable table pointer */
	PARG *cur_arg;		/* ptr to buffers argument list */
	char outseq[256];	/* output buffer for keystroke sequence */

	/* and get a buffer for it */
	varbuf = bfind(TEXT56, TRUE, BFINVS);
/*		   "Variable list" */
	if (varbuf == NULL || bclear(varbuf) == FALSE) {
		mlwrite(TEXT57);
/*			"Can not display variable list" */
		return(FALSE);
	}

	/* let us know this is in progress */
	mlwrite(TEXT58);
/*		"[Building variable list]" */

	/* build the environment variable list */
	for (uindex = 0; uindex < NEVARS; uindex++) {

		/* add in the environment variable name */
		strcpy(outseq, "$");
		strcat(outseq, envars[uindex]);
		pad(outseq, 14);
	        
		/* add in the value */
		strcat(outseq, gtenv(envars[uindex]));

		/* and add it as a line into the buffer */
		if (addline(varbuf, outseq) != TRUE)
			return(FALSE);
	}

	/* build all the user variable lists */
	ut = uv_head;
	while (ut) {

		/* a blank line, please.... */
		if (addline(varbuf, "") != TRUE)
			return(FALSE);

		/* make a header for this list */
		strcpy(outseq, "----- ");
		if (ut->bufp == (BUFFER *)NULL)
			strcat(outseq, "Global User Variables");
		else {
			strcat(outseq, "Defined in ");
			strcat(outseq, ut->bufp->b_bname);
			if (ut->bufp->b_numargs > 0) {
				strcat(outseq, "(");
				cur_arg = ut->bufp->b_args;
				while (cur_arg) {
					if (cur_arg != ut->bufp->b_args)
						strcat(outseq, ", ");
					strcat(outseq, cur_arg->name);
					cur_arg = cur_arg->next;
				}
				strcat(outseq, ")");
			}
		}
		strcat(outseq, " -----");

		/* and add it as a line into the buffer */
		if (addline(varbuf, outseq) != TRUE)
			return(FALSE);

		/* build this list */
		for (uindex = 0; uindex < ut->size; uindex++) {
			if (ut->uv[uindex].u_name[0] == 0)
				break;
	
			/* add in the user variable name */
			strcpy(outseq, "%");
			strcat(outseq, ut->uv[uindex].u_name);
			pad(outseq, 14);
		        
			/* add in the value */
			strcat(outseq, ut->uv[uindex].u_value);
	
			/* and add it as a line into the buffer */
			if (addline(varbuf, outseq) != TRUE)
				return(FALSE);
		}
		ut = ut->next;
	}

	/* display the list */
	wpopup(varbuf);
	mlerase();	/* clear the mode line */
	return(TRUE);
}

/*	describe-functions	Bring up a fake buffer and list the
				names of all the functions
*/

int PASCAL NEAR desfunc(f, n)

int f,n;	/* prefix flag and argument */

{
	register BUFFER *fncbuf;/* buffer to put function list into */
	register int uindex;	/* index into funcs table */
	char outseq[80];	/* output buffer for keystroke sequence */

	/* get a buffer for the function list */
	fncbuf = bfind(TEXT211, TRUE, BFINVS);
/*		   "Function list" */
	if (fncbuf == NULL || bclear(fncbuf) == FALSE) {
		mlwrite(TEXT212);
/*			"Can not display function list" */
		return(FALSE);
	}

	/* let us know this is in progress */
	mlwrite(TEXT213);
/*		"[Building function list]" */

	/* build the function list */
	for (uindex = 0; uindex < NFUNCS; uindex++) {

		/* add in the environment variable name */
		strcpy(outseq, "&");
		strcat(outseq, funcs[uindex].f_name);

		/* and add it as a line into the buffer */
		if (addline(fncbuf, outseq) != TRUE)
			return(FALSE);
	}

	if (addline(fncbuf, "") != TRUE)
		return(FALSE);

	/* display the list */
	wpopup(fncbuf);
	mlerase();	/* clear the mode line */
	return(TRUE);
}

VOID PASCAL NEAR pad(s, len)	/* pad a string to indicated length */

char *s;	/* string to add spaces to */
int len;	/* wanted length of string */

{
	while (strlen(s) < len) {
                strcat(s, "          ");
		s[len] = 0;
	}
}
